/**
 * 
 */
package com.vision.core.sv.util;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.hibernate.transform.ResultTransformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Reflection-based implementation for {@link ResultTransformer} for a specified
 * type.
 * 
 * @param <T>
 * 
 * @author Mark
 *
 */
public class SimpleTransformer<T> implements ResultTransformer {

	private static final long serialVersionUID = 1L;
	
	private static final Logger log = LoggerFactory.getLogger(SimpleTransformer.class);
	
	/**
	 * The type of object this transformer will construct.
	 */
	private Class<T> clazz;
	
	/**
	 * The list of properties (field names in {@link #clazz}) whose data were
	 * queried.
	 */
	private List<String> properties;
	
	/**
	 * Cache for {@link Field}s to minimize calls to {@link Class#getDeclaredField(String)}
	 * during the processing of tuples.
	 */
	private Map<String, Field> fieldCache = new HashMap<String, Field>();
	
	public static <T> SimpleTransformer<T> create(Class<T> clazz, String[] properties) {
		return new SimpleTransformer<T>(clazz, properties);
	}
	
	public static <T> SimpleTransformer<T> create(Class<T> clazz, List<String> properties) {
		return new SimpleTransformer<T>(clazz, properties);
	}
	
	protected SimpleTransformer(Class<T> clazz, String[] properties) {
		this(clazz, Arrays.asList(properties));
	}
	
	protected SimpleTransformer(Class<T> clazz, List<String> properties) {
		this.clazz = clazz;
		this.properties = properties;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public List<T> transformList(List collection) {
		return new ArrayList<T>(collection);
	}

	/**
	 * Returns an instance of the object specified. The instance is constructed
	 * through its zero-arg constructor and populated through {@link #properties}
	 * using reflection.
	 * 
	 * It is assumed that the order of the fields in {@link #properties} and in
	 * <tt>tuple</tt> is the same.
	 * 
	 * @param tuple
	 * @param aliases
	 */
	@Override
	public T transformTuple(Object[] tuple, String[] aliases) {
		if (properties.size() != tuple.length)
			throw new RuntimeException("The number of columns expected is not the same as the one returned!");
		
		try {
			T object = clazz.newInstance();
			
			for (int i = 0; i < properties.size(); i++) {
				String property = properties.get(i);
				Field field = fieldCache.get(property);
				if (field == null)
					fieldCache.put(property, field = clazz.getDeclaredField(property));
				
				field.setAccessible(true);
				
				Object value = tuple[i];
				
				log.trace("Setting field: '{}' with value: '{}' for type: '{}'", new Object[]{ property, value, clazz.getName() });
				
				field.set(object, value);
			}
			
			return object;
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new RuntimeException(e);
		}
	}
	
}
